package com.qianfeng.ssmplatform.search.service.impl;


//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼            BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  


import com.fasterxml.jackson.databind.ObjectMapper;
import com.qianfeng.smsplatform.common.model.Standard_Submit;
import com.qianfeng.ssmplatform.search.cache.ParamsLocalCache;
import com.qianfeng.ssmplatform.search.service.DocService;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by jackiechan on 2021/7/22 11:04
 *
 * @author jackiechan
 * 请记住 2021-06-24 这个让我变成 SB 的日子
 */
@Service
public class DocServiceImpl implements DocService {
    @Value("${elasticsearch.index.name}")
    private String index;


    private ParamsLocalCache paramsLocalCache;

    @Autowired
    public void setParamsLocalCache(ParamsLocalCache paramsLocalCache) {
        this.paramsLocalCache = paramsLocalCache;
    }

    private ObjectMapper objectMapper;

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
        //TODO 我们在这里单独设置日期格式化而不是直接给目标类上面添加jsonformat注解的原因是
        // 因为直接加在目标类上面会导致所有依赖目标类的项目都要添加jackson依赖,而我们有些模块是不需要jackson
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        objectMapper.setDateFormat(simpleDateFormat);
    }

    private RestHighLevelClient highLevelClient;

    @Autowired
    public void setHighLevelClient(RestHighLevelClient highLevelClient) {
        this.highLevelClient = highLevelClient;
    }

    @Override
    public void addDoc(Standard_Submit standard_submit) throws IOException {
        IndexRequest request = new IndexRequest(index);//设置要操作的库
        //request.source(standard_submit);
        String json = objectMapper.writeValueAsString(standard_submit);
        request.source(json, XContentType.JSON);
        highLevelClient.index(request, RequestOptions.DEFAULT);
    }

    @Override
    public void updateDoc(Standard_Submit standard_submit) throws IOException {
        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.index(index);
        Map targetData = queryDataByCidAndPhoneAndSrcId(standard_submit.getClientID(), standard_submit.getSrcSequenceId(), standard_submit.getDestMobile());

        if (targetData != null) {//如果能查询到当前的数据,则进行更新
            Object id = targetData.get("_id");
            updateRequest.id((String) id);
            updateRequest.doc(objectMapper.writeValueAsString(standard_submit), XContentType.JSON);
            highLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        }
//        updateRequest.do
//
//        highLevelClient.update()
    }

    @Override
    public Map queryDataByCidAndPhoneAndSrcId(int clientId, long srcId, String phoneNum) throws IOException {
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        searchSourceBuilder.query(QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("clientID", clientId))
                .must(QueryBuilders.termQuery("srcSequenceId", srcId))
                .must(QueryBuilders.termQuery("destMobile", phoneNum)));

        searchRequest.source(searchSourceBuilder);

        SearchResponse searchResponse = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHit[] searchHits = searchResponse.getHits().getHits();
        if (searchHits != null) {
            for (SearchHit searchHit : searchHits) {

                Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                sourceAsMap.put("_id", searchHit.getId());//因为source中是不包含id的,所以我们单独获取一下id设置进去
                System.err.println(sourceAsMap);
                return sourceAsMap;
            }
        }
        return null;
    }

    /**
     * @param params 用户传递的参数,会根据不同的需求传递不同的参数过来
     * @return
     */
    @Override
    public List<Map> queryDataByMultiCondition(Map<String, String> params) throws IOException {
        // 条件会有很多,需要挨个遍历,我们使用什么类型的查询呢? term? match? boole?
        // 用户会不会要求高亮呢? 比如要求了怎么办? 没要求怎么办? 高亮是怎么高亮的?
        // 用户有没有要求分页呢? 要求了怎么办,没要求怎么办
        // 如果要新增条件(原本计划有五个不同的条件,想要增加到6 7 8 甚至更多,新增的条件到底是term还是match?)
//        String content = "";
//        Object mobile = params.get("mobile");
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//
//        if (!StringUtils.isEmpty(content)) {
//            boolQuery.must(QueryBuilders.matchQuery("messageContent", content));
//        }
//        if (!StringUtils.isEmpty(mobile)) {
//            boolQuery.must(QueryBuilders.termQuery("mobile", mobile));
//        }
        Map<String, Map<String, String>> paramsMap = paramsLocalCache.getParamsMap();//这是我们自己定义的所有的参数以及对应实际列明以及查询类型的map
        // highlight ,pagesize, pagenum
        //注意 map的移出方法会将移除的数据返回,除非这个key不存在
        String highlight = params.remove("highlight"); //用户传递的想要哪个列高亮,可能和实际的es中的列明不一致,需要通过映射获取
        String pagesize = params.remove("size");
        String pagenum = params.remove("from");
        String pretag = params.remove("pretag");
        String posttag = params.remove("posttag");
        int size = 10;//默认值
        int from = 0;//默认的起点

        if (!StringUtils.isEmpty(pagesize)) {
            size = Integer.parseInt(pagesize);
        }
        if (!StringUtils.isEmpty(pagenum)) {
            from = Integer.parseInt(pagenum); //前端传过来的就是算好的偏移量
        }
        //设置分页
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(size);
        String hightLightFiled = null;//因为如果设置了高亮,在后面获取数据的时候需要知道是获取哪个列的值,所以我们在这里定义一个变量,用于接收高亮的字段名,方便下面获取高亮的时候使用

        if (!StringUtils.isEmpty(highlight)) { //如果用户传递了高亮列

            HighlightBuilder highlightBuilder = new HighlightBuilder();
            //因为用户传递的高亮的列的名字实际上可能也和我们真正的列明不一致,所以我们需要根据用户传递的名字
            //从映射的map中去取出来真正的高亮列明
            Map<String, String> stringStringMap = paramsMap.get(highlight);
            if (stringStringMap != null) {
                hightLightFiled = stringStringMap.get("cloum");
                highlightBuilder.field(hightLightFiled);//设置真正的高亮的列
                ///高亮实际上是我们将内容用特殊的标签括起来的,所以才亮了
                //问题来了,我们这边是用什么标签让它亮起来的?                <span color='red'>
                //TODO <span color='red'> color后面不要带空格,否则会出现操作超时的异常
                highlightBuilder.preTags(StringUtils.isEmpty(pretag) ? "<span color='red'>" : pretag);
                highlightBuilder.postTags(StringUtils.isEmpty(posttag) ? "</span>" : posttag);
                highlightBuilder.fragmentSize(0);//设置返回完整的内容,否则只是一部分
                searchSourceBuilder.highlighter(highlightBuilder);//设置高亮查询
            }
        }


        //遍历用户传递的参数列表,将用户传递的参数挨个和我们的映射关系进行匹配,生成真正的查询条件
        for (Map.Entry<String, String> entry : params.entrySet()) {
            String key = entry.getKey();//传递的参数明 比如用户传递了 mobile=13888888888  key就是mobile
            String value = entry.getValue();//参数对应的值  比如用户传递了 mobile=13888888888 ,value就是13888888888

            if (!StringUtils.isEmpty(value)) {//不仅仅是传递了条件的名字,还要有值
                Map<String, String> currntparamMap = paramsMap.get(key);//根据当前用户传递的key获取到这个对应的实际查询内方式

                if (currntparamMap != null) {
                    String cloum = currntparamMap.get("cloum");//代表实际查询的列
                    String type = currntparamMap.get("type");//实际的查询类型 比如 match term
                    switch (type) {
                        case "term":
                            //比如用户传递的是mobile=138888888 我们实际应该查destMobile=138888888
                            boolQuery.must(QueryBuilders.termQuery(cloum, value));
                            break;
                        case "match":
                            boolQuery.must(QueryBuilders.matchQuery(cloum, value));
                            break;
                        case "range":
                            //当前参数是起点还是终点
                            //当前参数对应的另外一个匹配参数是什么,比如当前是起点参数,终点参数是什么
                            //当前参数对应的实际参数是什么
                            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(cloum);
                            String order = currntparamMap.get("tOrder");//顺序,也就是是起点还是终点,我们约定0是起点,1是终点
                            if ("0".equals(order)) {//是起点参数
                                rangeQuery.gte(value);
                            } else {
                                rangeQuery.lte(value);//终点参数
                            }
                            boolQuery.must(rangeQuery);
                            break;
                    }
                }
            }
        }

        searchSourceBuilder.query(boolQuery);
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits responseHits = response.getHits();
        long count = responseHits.getTotalHits().value;//当前条件下能查询到的数据的总条数
        params.put("resultTotalCount", count+"");//因为我们的返回值是一个list集合,无法把总条数返回,所以我们把它放到参数的map中,而这个map在外面的controller是可以直接操作的,有点类似于mybatis的主键回填
        List<Map> resultList = new ArrayList<>();
        for (SearchHit hit :responseHits.getHits()) {
            ///System.err.println(hit.getSourceAsString());
            Map<String, Object> source = hit.getSourceAsMap();
            //获取高亮数据
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields != null) {
                // System.err.println(highlightFields.entrySet());
                HighlightField highlightField = highlightFields.get(hightLightFiled);//获取到我们定义的高亮的区域数据
                if (highlightField != null) {
                    Text[] fragments = highlightField.getFragments();//获取高亮的内容片段
                    if (fragments != null) {//如果有高亮数据
                        //  fragments[0].string()
                        source.put(hightLightFiled, fragments[0].string());//用高亮的内容替换掉原始的内容
                    }
                }
            }

            resultList.add(source);
        }
        return resultList;
    }
}
